import concurrent.futures
import redis
import ipaddress
from ccolor import Colors

# 全局配置
MAX_WORKERS = 200  # 线程池大小
TIMEOUT = 2  # 连接超时时间


def scan_redis(host, port, password):
    """
    扫描Redis弱口令和未授权访问
    """
    try:
        client = redis.Redis(
            host=host,
            port=port,
            password=password,
            db=0,
            socket_timeout=TIMEOUT,
            socket_connect_timeout=TIMEOUT
        )

        # 测试连接
        if client.ping():
            result = f"{Colors.GREEN}[+] {host}:{port} 存在弱口令 -> {password}{Colors.RESET}"

            # 检测未授权访问
            try:
                client.config_set("dir", "/tmp")
                dir_info = client.config_get("dir")
                if dir_info.get("dir") == "/tmp":
                    result += f"\n{Colors.RED}[!] {host}:{port} 存在未授权访问漏洞{Colors.RESET}"
            except:
                pass

            client.close()
            return True, result

        client.close()
        return False, None

    except redis.exceptions.AuthenticationError:
        return False, None
    except Exception as e:
        return False, None


def check_unauth_access(host, port):
    """
    专门检测未授权访问
    """
    try:
        client = redis.Redis(
            host=host,
            port=port,
            password=None,
            socket_timeout=TIMEOUT,
            socket_connect_timeout=TIMEOUT
        )

        if client.ping():
            try:
                client.config_set("dir", "/tmp")
                dir_info = client.config_get("dir")
                if dir_info.get("dir") == "/tmp":
                    return True, f"{Colors.RED}[!] {host}:{port} 存在未授权访问漏洞{Colors.RESET}"
            except:
                pass

        return False, None

    except:
        return False, None
    finally:
        try:
            client.close()
        except:
            pass


def scan_single_redis(host, port, passwords):
    """
    扫描单个Redis实例
    """
    print(f"{Colors.CYAN}[*] 扫描主机: {host}:{port}{Colors.RESET}")

    # 先检查未授权访问
    success, message = check_unauth_access(host, port)
    if success:
        print(message)
        return

    # 扫描弱口令
    with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
        futures = [
            executor.submit(scan_redis, host, port, pwd)
            for pwd in passwords
        ]

        for future in concurrent.futures.as_completed(futures):
            success, message = future.result()
            if success:
                print(message)
                break


def scan_redis_network(network, port, passwords):
    """
    扫描Redis网段
    """
    try:
        network_obj = ipaddress.IPv4Network(network, strict=False)
        hosts = list(network_obj.hosts())

        print(f"{Colors.YELLOW}[*] 开始扫描网段: {network}{Colors.RESET}")
        print(f"{Colors.CYAN}[*] 包含 {len(hosts)} 台主机{Colors.RESET}")

        with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
            futures = []
            for ip in hosts:
                ip_str = str(ip)
                futures.append(
                    executor.submit(scan_single_redis, ip_str, port, passwords)
                )

            # 显示进度
            completed = 0
            for future in concurrent.futures.as_completed(futures):
                completed += 1
                if completed % 10 == 0 or completed == len(hosts):
                    print(
                        f"{Colors.CYAN}[*] 进度: {completed}/{len(hosts)} ({completed / len(hosts):.1%}){Colors.RESET}")

    except ValueError:
        print(f"{Colors.RED}[-] 无效的网段格式: {network}{Colors.RESET}")


def redis_run(host, port, passwds):
    """
    Redis扫描主函数
    """
    try:
        # 预处理密码列表
        passwords = [p.strip() for p in passwds if p.strip()]
        if not passwords:
            print(f"{Colors.RED}[-] 密码列表为空{Colors.RESET}")
            return

        print(f"{Colors.YELLOW}[*] 开始扫描Redis{Colors.RESET}")
        print(f"{Colors.CYAN}[*] 加载密码: {len(passwords)} 个{Colors.RESET}")

        if '/' in host:
            # 网段扫描模式
            scan_redis_network(host, port, passwords)
        else:
            # 单IP扫描模式
            scan_single_redis(host, port, passwords)

        print(f"{Colors.YELLOW}[*] 扫描完成{Colors.RESET}")

    except Exception as e:
        print(f"{Colors.RED}[-] 发生错误: {str(e)}{Colors.RESET}")


if __name__ == "__main__":
    # 测试代码
    test_passwords = ["", "123456", "redis", "password", "root"]
    redis_run("127.0.0.1", 6379, test_passwords)